﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace TelerikMvcGridCustomBindingHelper.Mapper
{
    public interface INullSubstitute
    {
        bool IsCaseInsensitive { get; }
        IList<object> NullValues();
    }

    public class NullSubstitutes : ConcurrentDictionary<NullSubstituteKey, INullSubstitute>
    {
        public bool HasSubstituteFor<TEntity, TViewModel>(GridPropertyInfo gridPropertyInfo, object value)
        {
            var hasSubstituteFor = ContainsKey(NullSubstituteKey.New(typeof(TEntity), typeof(TViewModel), gridPropertyInfo))
                   || ContainsKey(NullSubstituteKey.GlobalKey());

            if (hasSubstituteFor == false)
                return false;

            var substitute = GetSubstitutesFor<TEntity, TViewModel>(gridPropertyInfo);

            if (substitute == null)
                return false;

            if (value == null && substitute.NullValues().Any())
                return true;

            foreach (var nullValue in substitute.NullValues())
            {
                if (nullValue == value)
                {
                    return true;
                }

                if (Equals(nullValue, value))
                {
                    return true;
                }

                if (!(value is string && nullValue is string))
                    continue;

                if (string.Equals((string)nullValue, (string)value, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        public INullSubstitute GetSubstitutesFor<TEntity, TViewModel>(GridPropertyInfo gridPropertyInfo)
        {
            INullSubstitute nullSubstitute;

            var tryGetValue = TryGetValue(NullSubstituteKey.New(typeof(TEntity), typeof(TViewModel), gridPropertyInfo), out nullSubstitute);

            if (tryGetValue == false)
                tryGetValue = TryGetValue(NullSubstituteKey.GlobalKey(), out nullSubstitute);

            if (tryGetValue == false)
                return null;

            return nullSubstitute;
        }
    }

    public struct NullSubstituteKey : IEquatable<NullSubstituteKey>
    {
        public readonly Type EntityType;
        public readonly Type ViewModelType;
        public readonly GridPropertyInfo GridPropertyInfo;

        private NullSubstituteKey(Type entityType, Type viewModelType, GridPropertyInfo gridPropertyInfo)
        {
            EntityType = entityType;
            ViewModelType = viewModelType;
            GridPropertyInfo = gridPropertyInfo;
        }

        public static NullSubstituteKey New(Type entityType, Type viewModelType, GridPropertyInfo gridPropertyInfo)
        {
            return new NullSubstituteKey(entityType, viewModelType, gridPropertyInfo);
        }

        public static NullSubstituteKey GlobalKey()
        {
            var type = typeof(INullSubstitute);
            var gpi = new GridPropertyInfo(type, type.FullName);
            return new NullSubstituteKey(type, type, gpi);
        }

        #region IEquatable<NullSubstituteKey>

        public bool Equals(NullSubstituteKey other)
        {
            return other.EntityType == EntityType
                   && other.ViewModelType == ViewModelType
                   && GridPropertyInfo != null
                   && (other.GridPropertyInfo.PropertyType == GridPropertyInfo.PropertyType
                   && Equals(other.GridPropertyInfo.PropertyPath, GridPropertyInfo.PropertyPath));
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof(NullSubstituteKey)) return false;
            return Equals((NullSubstituteKey)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var result = ToString().GetHashCode();

                if (EntityType != null) result = (result * 397) ^ EntityType.GetHashCode();

                if (ViewModelType != null) result = (result * 397) ^ ViewModelType.GetHashCode();

                if (GridPropertyInfo != null)
                {
                    if (GridPropertyInfo.PropertyPath != null)
                        result = (result * 397) ^ GridPropertyInfo.PropertyPath.GetHashCode();
                    if (GridPropertyInfo.PropertyType != null)
                        result = (result * 397) ^ GridPropertyInfo.PropertyType.GetHashCode();
                }

                return result;
            }
        }

        #endregion
    }
}